package org.bugstat.renderers.html;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.bugstat.common.reports.DateKey;
import org.bugstat.common.reports.DoubleValue;
import org.bugstat.common.reports.IntegerValue;
import org.bugstat.common.reports.Report;
import org.bugstat.common.reports.ReportKey;
import org.bugstat.common.reports.ReportValue;
import org.bugstat.common.reports.ReportValueName;
import org.bugstat.common.reports.StringValueName;
import org.bugstat.common.reports.TupleIntegerValue;
import org.bugstat.common.reports.TupleValueName;
import org.bugstat.common.utils.ProgressListener;
import org.bugstat.renderers.Renderer;
import org.bugstat.renderers.RenderersString;
import org.bugstat.renderers.ReportInformation;
import org.bugstat.renderers.ReportRenderer;
import org.bugstat.renderers.ReportInformation.ImportantDate;

@Renderer
public class HtmlReportRenderer implements ReportRenderer
{
    private static final String HTML_FOLDER = "html";
    private static final DateFormat DATE_FORMAT = new SimpleDateFormat("MMMMMMMM yyyy");

    private static Log log = LogFactory.getLog(HtmlReportRenderer.class);

    public String getName()
    {
        return RenderersString.TEXT_HTML_RENDERER;
    }

    public Collection<File> renderReports(List<Report> reports, ReportInformation reportInformation, File folder, ProgressListener listener) throws IOException
    {
        if ( reports == null ) throw new IllegalArgumentException("Invalid reports specified");
        if ( folder == null ) throw new IllegalArgumentException("Invalid folder specified");
        if ( reportInformation == null ) throw new IllegalArgumentException("Invalid report information specified");
        if ( !folder.isDirectory() ) throw new IllegalArgumentException("Specified folder is not a folder: " + folder.getAbsolutePath());

        final File outputFolder = new File(folder, HTML_FOLDER);
        outputFolder.mkdirs();

        final Collection<File> result = new HashSet<File>();

        int currentReport = 0;
        int totalReports = reports.size();

        final File reportsFolder = new File(outputFolder, "reports");
        reportsFolder.mkdirs();

        for (int i = 1; i <= reports.size(); i++)
        {
            final File outputFile = new File(reportsFolder, i + ".get");
            result.add(outputFile);

            FileWriter writer = null;
            try
            {
                writer = new FileWriter(outputFile);

                final Report report = reports.get(i - 1);

                log.debug("Rendering HTML report for " + report + "...");

                writeReport(i, report, writer);

                currentReport += 1;
                if ( listener != null ) listener.progressReported(Math.round(90F * (currentReport / totalReports)));
            }
            finally
            {
                if ( writer != null ) writer.close();
            }
        }

        // Create page meta data file
        log.debug("Rendering HTML metadata...");
        result.add(writeToFile(reportsFolder, "page_metadata.get", retrievePageMetaData( reportInformation )));
        result.add(writeToFile(reportsFolder, "report_descriptor.get", retrieveReportsDescriptor(reports)));
        result.add(writeToFile(reportsFolder, "important_dates.get", retrieveImportantDates( reportInformation ) ) );

        if ( listener != null ) listener.progressReported(95);

        log.debug("Packaging HTML framework...");
        packageHtmlFramework( outputFolder );
        
        if ( listener != null ) listener.progressReported(100);

        log.info("HTML reports generated");

        return null;
    }

    private void writeReport(int id, Report report, FileWriter writer) throws IOException 
    {
        long[] minMaxValues = retriveMixMaxValues( report );
        
        final String header = MessageFormat.format( "'{' \"id\": {0}, " +
        		                                        "\"name\": {1}, \"group\": {2}, \"keyName\": {3}, " +
        		                                        "\"visible\": {4}, \"captions\": {5}, " +
        		                                        "\"minValue\": {6}, \"maxValue\": {7}, " +
        		                                        "\"type\": {8}, ",
                                                    String.valueOf( id ),
                                                    JsonUtils.toJSON( report.getName() ),
                                                    JsonUtils.toJSON( report.getGroup()) ,
                                                    JsonUtils.toJSON( report.getKeyName() ),
                                                    report.isVisibleByDefault(),                                                    
                                                    retrieveCaptions( report ),
                                                    Long.toString( minMaxValues[0] ),
                                                    Long.toString( minMaxValues[1] ),
                                                    JsonUtils.toJSON( report.getType().toString() )
                                                  ); 
        writer.write( header );
        writeData( report, writer );
        writer.write( "}");
    }

    private String retrieveCaptions(Report report)
    {
        final StringBuffer result = new StringBuffer();
        result.append("[");
        result.append(JsonUtils.toJSON(report.getKeyName()));
        result.append(",");

        final ReportValueName valueName = report.getValueName();
        if ( valueName instanceof StringValueName )
        {
            result.append(JsonUtils.toJSON(((StringValueName) valueName).getName()));
        }
        else if ( valueName instanceof TupleValueName )
        {
            final TupleValueName tupleValueName = (TupleValueName) valueName;

            for (int i = 0; i < tupleValueName.getLength(); i++)
            {
                if ( i != 0 ) result.append(",");
                result.append(JsonUtils.toJSON(tupleValueName.getName(i)));
            }
        }
        else
        {
            log.error("Programming error: unknown report value name: " + valueName);

            return "?";
        }

        result.append("]");

        return result.toString();
    }

    private long[] retriveMixMaxValues(Report report)
    {
        double maxValue = Double.NEGATIVE_INFINITY;
        double minValue = Double.POSITIVE_INFINITY;
        
        for ( ReportKey key : report.getKeys() )
        {
            final ReportValue value = report.getValueFor( key );
            final double valueNumber;
            if ( value instanceof IntegerValue )
            {
                valueNumber = ((IntegerValue)value).getValue();
            }
            else if ( value instanceof DoubleValue )
            {
                valueNumber = ((DoubleValue)value).getValue();
            }
            else if ( value instanceof TupleIntegerValue )
            {
                valueNumber = ((TupleIntegerValue)value).getMaxValue();
            }
            else
            {
                valueNumber = -1;
            }
                 
            if ( valueNumber > maxValue ) maxValue = valueNumber;
            if ( valueNumber < minValue ) minValue = valueNumber;
        }       
        
        return new long[] { Math.round( minValue ), Math.round( maxValue ) };
    }

    
    private String writeData(Report report, final Writer writer) throws IOException
    {
        writer.write("\"report_data\": [");

        final List<? extends ReportKey> keys = report.getKeys();

        for (int i = 0; i < keys.size(); i++)
        {
            final ReportKey key = keys.get(i);

            if ( i != 0 ) writer.write(",");

            writer.write("[");
            writer.write(retrieveKey(key));
            writer.write(",");
            writer.write(retrieveValue(report.getValueFor(key)));

            writer.write("]");
        }

        writer.write("]");

        return report.toString();
    }

    private String retrieveKey(ReportKey reportKey)
    {
        if ( reportKey instanceof DateKey )
        {
            final DateKey dateKey = (DateKey) reportKey;
            final Date date = dateKey.getDate();

            return String.valueOf(date.getTime());
        }
        else
        {
            log.error("Programming error: unknown report key: " + reportKey);

            return "?";
        }
    }

    private String retrieveValue(ReportValue value)
    {
        if ( value instanceof IntegerValue )
        {
            final IntegerValue intValue = (IntegerValue) value;
            return String.valueOf(intValue.getValue());
        }
        else if ( value instanceof DoubleValue )
        {
            final DoubleValue doubleValue = (DoubleValue) value;
            return String.valueOf(doubleValue.getValue());
        }
        else if ( value instanceof TupleIntegerValue )
        {
            final StringBuffer result = new StringBuffer();

            final TupleIntegerValue tupleValue = (TupleIntegerValue) value;

            result.append( "[" );
            for (int i = 0; i < tupleValue.getLength(); i++)
            {
                if ( i != 0 ) result.append(',');
                result.append(String.valueOf(tupleValue.getValue(i)));

            }
            result.append( "]" );

            return result.toString();
        }
        else
        {
            log.error("Programming error: unknown report value: " + value);

            return "?";
        }
    }

	private String retrieveReportsDescriptor(List<Report> reports) 
	{
		final StringBuffer buffer = new StringBuffer();
		buffer.append("[");
		
		for ( int i = 1; i <= reports.size(); i++ )
		{
			final Report report = reports.get( i - 1);
			
			buffer.append( MessageFormat.format( "'{'\"name\": {0}, \"id\": {1}, \"visible\": {2}, \"group\": {3}'}',", 
												JsonUtils.toJSON( report.getName() ), 
												JsonUtils.toJSON( String.valueOf( i ) ), 
												report.isVisibleByDefault(), 
												JsonUtils.toJSON( report.getGroup() ) ) );
		}
		if ( !reports.isEmpty() ) buffer.setLength( buffer.length() - 1 );
		buffer.append("]");
		
        return buffer.toString();
    }

    private String retrievePageMetaData(ReportInformation reportInformation)
    {
        final String reportName = reportInformation.getReportName();
        final String from = toString( reportInformation.getReportInterval().getStartDate() );
        final String to = toString( reportInformation.getReportInterval().getEndDate() );

        return MessageFormat.format("'{'\"name\": {0}, \"subName\": \"From {1} to {2}\" '}'", JsonUtils.toJSON(reportName), from, to);
    }

    private File writeToFile(File folder, String fileName, String content) throws IOException
    {
        final File outputFile = new File(folder, fileName);
        FileWriter out = null;

        try
        {
            out = new FileWriter(outputFile);
            out.write(content);
        }
        finally
        {
            if ( out != null ) out.close();
        }

        return outputFile;
    }


    private String retrieveImportantDates(ReportInformation reportInformation)
    {
        final StringBuffer datesArray = new StringBuffer();
        for ( ImportantDate importantDate : reportInformation.getImportantDates() )
        {
            datesArray.append( "[ " );
            datesArray.append( importantDate.getDate().getTime() ).append( ',' );
            datesArray.append( JsonUtils.toJSON( importantDate.getLabel() ) );
            datesArray.append( "]," );
        }
        if ( datesArray.length() > 0 ) datesArray.setLength( datesArray.length() - 1 ); // Last comma
        
        return "{\"dates\": [" + datesArray + "]}";
    }
    
    private void packageHtmlFramework(final File outputFolder ) throws IOException
    {
        outputFolder.mkdirs();
        
        final File resourcesFodler = new File("resources");
        final File[] files = resourcesFodler.listFiles();
        if ( files != null && files.length > 0 )
        {
            File resourcesFile = null;
            for (File file : files)
            {
                if ( file.getName().startsWith("html_reports") && 
                     file.isFile() )
                {
                    resourcesFile = file;
                    break;
                }
            }
            if ( resourcesFile == null ) throw new RuntimeException("Programming error: distribution corrupt, 'html_reports' not found");

            // Unzip reports into temporary folder
            final File systemTmpFolder = File.createTempFile("dummy_temp", "").getParentFile();
            final File tempFolder = new File( systemTmpFolder, "html_reports_target" );
            tempFolder.mkdirs();
            
            ZipUtils.unzip(resourcesFile, tempFolder);
            
            // Copy everything from temporary folder to output folder
            final File[] unzipedEntris = tempFolder.listFiles();
            if ( unzipedEntris != null && files.length > 0 )
            {
                File htmlReportsFolder= null;
                for (File file : files)
                {
                    if ( file.getName().startsWith("html_reports") && 
                         file.isDirectory() )
                    {
                        htmlReportsFolder = file;
                        break;
                    }
                }
                if ( htmlReportsFolder == null ) throw new RuntimeException("Programming error: distribution corrupt, 'html_reports' not found after unzipping");
                
                FileUtils.copyDirectory( htmlReportsFolder, outputFolder );
            }
            
            final boolean folderRemoved = tempFolder.delete();
            if ( !folderRemoved ) log.error("Unable to remove " + tempFolder.getAbsolutePath() );
        }
        else
        {
            throw new RuntimeException("Programming error: distribution corrupt, resources not found");
        }
    }
    
    private static String toString(Date date)
    {
        return DATE_FORMAT.format( date );
    }
}
